Skip to content

feat(THI-51): modernize revenue & expenses tab - #21

Merged
tdody merged 2 commits into
mainfrom
thibaultdody/thi-51-revenue-expenses-tab-assumptions-seasonal-bars-sticky
Apr 19, 2026
Merged

feat(THI-51): modernize revenue & expenses tab#21
tdody merged 2 commits into
mainfrom
thibaultdody/thi-51-revenue-expenses-tab-assumptions-seasonal-bars-sticky

Conversation

@tdody

@tdody tdody commented Apr 19, 2026

Copy link
Copy Markdown
Owner

Summary

Rebuilds the Revenue & Expenses tab around the THI-45/46 design system with a 2-column (1fr + 340px) sticky layout, plus a new <SeasonalBars> primitive.

New primitive

  • shared/SeasonalBars.tsx — 12-bar SVG chart with peak-month color differentiation (accent for peak, ink-3/50% for off-peak). Exports derivePeakMask(peakMonths) that centers N peak months around July.

Main column (STR)

  • Segmented rental-type switcher replaces the old sky / violet pill buttons
  • FormSection: Revenue — nightly rate, cleaning fee, stay length, rental delay, platform fee %, property mgmt %
  • Occupancy panel — Toggle for seasonal mode; when on: Peak Months / Peak Occupancy / Off-Peak Occupancy / Weighted Effective sliders + <SeasonalBars> showing the implied 12-month profile. Advanced <details> exposes the existing MonthlyProfileEditor for per-month overrides.
  • FormSection: Operating Expenses (8 fields) — cleaning cost / turn, utilities, supplies, marketing, software, lawn & snow, other monthly, insurance (annual)
  • FormSection: Reserves (3 SliderFields) — maintenance / capex / damage
  • FormSection: Professional Fees — accounting, legal
  • <details> Vermont tax configuration (collapsible) — 5 % fields + registration fee + Platform Remits Tax <Toggle>
  • <details> Growth & Exit (collapsible, shared fields)

LTR

Mirrors the STR structure with lease-focused revenue (monthly rent, lease duration, pet rent, late fee, vacancy rate, lease-up), 8 operating expenses, 2-slider reserves, and the shared Growth & Exit block.

Sticky preview aside

Border-rule card showing Monthly Revenue, Monthly Expenses, Monthly Cashflow fetched from getResults / getLTRResults. Refreshes on mount and after every save. Positive cashflow in accent tone; negative in negative tone.

Preserved behavior

  • STR ↔ LTR swap
  • Shared-field sync between STR and LTR forms
  • Dual-persistence save (active type first, then shared to the other)
  • MonthlyProfileEditor (now under Advanced disclosure; token-ified colors)
  • All callback props from the detail shell unchanged — RevenueExpensesTab only gains a new propertyId prop, threaded through in PropertyDetail.tsx

Scope notes

  • The rewrite drops most inline tooltips in favor of the cleaner FormSection subtitles; fields still accept the tooltip prop via CurrencyInput / PercentInput where useful (tooltip copy preserved in the file — can re-surface via hint prop in a polish follow-up).
  • Seasonal model exposed to the UI for the first time (was in STRAssumptions but not editable before THI-51).

Test plan

  • npm run build — passes, CSS 54.19 kB, JS 464.21 kB (-8 kB from dead-code elimination), 117 modules
  • Dev run: STR scenario — toggle seasonal, drag peak months / peak / off-peak sliders, SeasonalBars redraws with accent/ink-3 peaks
  • Advanced disclosure opens the monthly profile editor; templates load; per-month edits persist
  • Switch to LTR — left column swaps fields, shared fields (growth / exit / insurance / etc.) stay in sync after save
  • Save: preview aside refreshes with new monthly revenue / expenses / cashflow
  • VT tax collapsible opens/closes; Platform Remits Tax toggle updates state
  • Dark mode: all FormSections, sliders, SeasonalBars, preview card token-driven

🤖 Generated with Claude Code

tdody and others added 2 commits April 19, 2026 17:39
Rebuild the Revenue & Expenses tab around the THI-45/46 design system with
a 2-column (1fr + 340px) sticky layout.

New primitive
- shared/SeasonalBars.tsx: 12-bar SVG chart with peak-month color
  differentiation; exports derivePeakMask helper that centers N peak months
  around July. Used in the seasonal occupancy panel.

Main-column structure (STR)
- Segmented rental-type switcher at top (STR / LTR)
- FormSection: Revenue — nightly rate, cleaning fee, stay length, rental
  delay, platform fee %, property mgmt %
- Occupancy panel (custom FormSection) — Toggle for seasonal mode, sliders
  for Peak Months / Peak Occupancy / Off-Peak Occupancy / Weighted Effective
  (read-only), SeasonalBars chart with peak/off-peak coloring. Advanced
  <details> exposes the existing MonthlyProfileEditor for per-month overrides.
- FormSection: Operating Expenses (8 fields) — cleaning cost/turn, utilities,
  supplies, marketing, software, lawn & snow, other monthly, insurance
- FormSection: Reserves (3 SliderFields) — maintenance / capex / damage
- FormSection: Professional Fees — accounting, legal
- <details> Vermont tax configuration (collapsible) — 5 %-fields + registration
  fee + Platform Remits Tax Toggle
- <details> Growth & Exit (shared fields, collapsible)

LTR structure mirrors STR with lease-focused revenue fields, 8 operating
expenses, 2-slider reserves, and the shared Growth & Exit block.

Sticky preview aside
- Border-rule card showing Monthly Revenue, Monthly Expenses, Monthly
  Cashflow fetched from getResults / getLTRResults. Refreshes on mount and
  after every save. Positive cashflow in accent; negative in negative tone.

Preserved behavior
- STR ↔ LTR switch (Segmented now replaces the sky/violet pill buttons)
- Shared-field sync between STR/LTR forms via updateSharedField
- Dual-persistence save logic (active type first, then sync shared to the
  other type)
- MonthlyProfileEditor still available (now under Advanced details disclosure)
- All tooltip-driven helper text kept (the fields still accept tooltips via
  their existing props, though the rewrite defers surfacing most copy to
  keep the layout tight — can re-add with the hint prop in follow-up)

Wiring
- PropertyDetail.tsx now passes `propertyId` into RevenueExpensesTab so the
  preview can call getResults / getLTRResults.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
SeasonalBars.tsx was exporting both a component and the derivePeakMask
helper, which breaks Vite fast refresh. Inline the helper into
RevenueExpensesTab (its only consumer) and keep SeasonalBars as a pure
component export.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@tdody
tdody merged commit 3558ca7 into main Apr 19, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant